// $Id: Game.cpp 128 2007-11-17 15:49:11Z bram $


/**
 * Game class definition file.
 *
 * @file Game.h
 * @author Bram Bonn&eacute;
 * @author Wim Leers
 */


#include "Game.h"


//----------------------------------------------------------------------------
// Constants.

const int Game::SIZE = 3;
const string Game::FILENAME = "savegame.rsg"; // "rsg" voor Reversi Saved Game


//----------------------------------------------------------------------------
// Con- & destructors.

/**
 * Constructor voor een nieuw spel.
 *
 * @param p1Name
 *   De naam van de eerste speler.
 * @param p2Name
 *   De naam van de tweede speler.
 *
 * @author Wim Leers
 */
Game::Game(string p1Name, string p2Name) {
	m_active = BLACK; // Zwart begint altijd.
	m_moveNumber = 0; // Beginnen bij 0 zetten.

	// Zoek de spelers op in de DB, maak nieuwe spelers indien niet gevonden.
	m_player1 = GetOrCreatePlayer(p1Name);
	m_player2 = GetOrCreatePlayer(p2Name);

	// Als geen saved game kan ingeladen worden, start een nieuw spel.
	if (Load())
		m_history.push(*m_board);
	else {
		m_board = new Board(SIZE);
		SetStartPosition();
	}
}

/**
 * Destructor.
 *
 * @author Wim Leers
 * @author Bram Bonn&acute;
 */
Game::~Game(void) {
	m_player1->IncreasePlays();
	m_player2->IncreasePlays();

	if (CountPieces(BLACK) > CountPieces(WHITE)) //Verhoog de score van de winnaar.
		m_player1->IncreaseVictories();
	else if (CountPieces(BLACK) < CountPieces(WHITE))
		m_player2->IncreaseVictories();

	// Sla de geschiedenis op.
	if (!this->BothMovePossible())
		Save();

	// Verwijder de geschiedenis.
	while (m_history.size() != 0)
		m_history.pop();
	delete m_board;

	// We moeten m_player1 en m_player2 niet verwijderen, omdat deze in de
	// PlayerDB zitten, en de PlayerDB vernietigt alle Player objecten zelf.
}


//----------------------------------------------------------------------------
// Public methods.

/**
 * Telt het aantal vakjes op het bord die een bepaalde kleur hebben.
 *
 * @param piece
 * 	De kleur die geteld moet worden.
 *
 * @author Bram Bonn&eacute;
 */
int Game::CountPieces(Piece piece) const {
	int aantal = 0;

	for (int i = 0; i < SIZE; i ++)
		for (int j = 0; j < SIZE; j ++)
			if (m_board->GetPiece(j, i) == piece)
				aantal ++;
	return aantal;
}

/**
 * Voert een zet uit indien deze geldig is.
 *
 * @param piece
 * 	De kleur die gezet moet worden.
 * @param x
 * 	x-coördinaat
 * @param y
 * 	y-coördinaat
 * @return
 *	Of de zet gelukt is of niet.
 *
 * @author Bram Bonn&eacute;
 * @author Wim Leers
 */
bool Game::TryPieceMove(Piece piece, int x, int y) {
	// Controleer of het stuk van het juiste type is.
	if (piece != m_active)
		throw(InternalException(
			"Game::TryPieceMove()",
			"Type van zet komt niet overeen met huidige speler."
		));

	if (IsValidMove(piece, x, y)) {
		// Bij de eerste manuele zet, sla ook het startbord op.
		if (m_moveNumber == 4)
			m_history.push(*m_board);

		m_board->SetPiece(piece, x, y);
		CheckAllDirections(piece, x, y, true); //Alle elementen flippen waarbij dit nodig is.
		m_moveNumber++;
		m_active.Toggle(); //Laat de andere speler aan de beurt.
		m_history.push(*m_board);
		return true;
	}
	else
		return false;
}

/**
 * Kijkt of er nog iets gedaan kan worden, en wisselt eventueel van speler als
 * dat niet zo is.
 *
 * @return
 *	Of er nog zetten mogelijk zijn.
 *
 * @author Bram Bonn&eacute;
 */
bool Game::BothMovePossible() {
	if (!ValidMovePossible(m_active)) {
		//Als de huidige speler niets meer kan zetten, andere speler aan de beurt laten.
		m_active.Toggle();
		if (!ValidMovePossible(m_active)) //Als deze ook niets kan, is er niets meer mogelijk
			return 0;
		else
			return 1;
	}
	else
		return 1;
}


//----------------------------------------------------------------------------
// Private methods.


/**
 * Haal de speler op uit de DB of creëer een nieuwe speler en voeg die dan toe
 * aan de DB.
 *
 * @param playerName
 *   De naam van de speler die opgezocht of gecreëerd moet worden.
 * @return
 *   Een pointer naar het Player object.
 */
Player * Game::GetOrCreatePlayer(string playerName) {
	Player * p;

	p = m_db.GetPlayer(playerName);
	if (p == NULL) {
		p = new Player(playerName);
		m_db.AddPlayer(p);
	}
	return p;
}

/**
 * Zet de beginpositie van het bord
 *
 * @param board
 * 	Het spelbord.
 *
 * @author Bram Bonn&eacute;
 */
void Game::SetStartPosition(void) {
	m_board->SetPiece(BLACK, SIZE/2 - 1, SIZE/2);
	m_board->SetPiece(WHITE, SIZE/2 - 1, SIZE/2 - 1);
	m_board->SetPiece(BLACK, SIZE/2, SIZE/2 - 1);
	m_board->SetPiece(WHITE, SIZE/2, SIZE/2);
	m_moveNumber += 4;
}

/**
 * Verandert alle elementen tussen de twee coördinaten van kleur.
 *
 * @param x1
 * 	x-coördinaat 1e element
 * @param y1
 * 	y-coördinaat 1e element
 * @param x2
 * 	x-coördinaat 2e element
 * @param y2
 * 	y-coördinaat 2e element
 *
 * @author Bram Bonn&eacute;
 */
void Game::FlipPieces(int x1, int y1, int x2, int y2, Directions direction) const {
	switch (direction) {
		case RIGHT:
			for (x1 ++; x1 < x2; x1 ++)
				m_board->TogglePiece(x1, y1);
			break;
		case LEFT:
			for (x1 --; x1 > x2; x1 --)
				m_board->TogglePiece(x1, y1);
			break;
		case UP:
			for (y1 ++; y1 < y2; y1 ++)
				m_board->TogglePiece(x1, y1);
			break;
		case DOWN:
			for (y1 --; y1 > y2; y1 --)
				m_board->TogglePiece(x1, y1);
			break;
		case RIGHTUP:
			for (x1 ++, y1 ++; x1 < x2; x1 ++, y1 ++)
				m_board->TogglePiece(x1, y1);
			break;
		case LEFTDOWN:
			for (x1 --, y1 --; x1 > x2; x1 --, y1 --)
				m_board->TogglePiece(x1, y1);
			break;
		case RIGHTDOWN:
			for (x1 ++, y1 --; x1 < x2; x1 ++, y1 --)
				m_board->TogglePiece(x1, y1);
			break;
		case LEFTUP:
			for (x1 --, y1 ++; x1 > x2; x1 --, y1 ++)
				m_board->TogglePiece(x1, y1);
			break;
	}
}

/**
 * Kijkt of er een zet mogelijk is voor de speler met kleur 'piece' door het bord
 * af te lopen en te kijken of er in één van de richtingen iets gebeurt.
 *
 * @param piece
 * 	De kleur waarvoor gecheckt moet worden.
 * @return
 *	Of er al dan niet een zet mogelijk is.
 */
bool Game::ValidMovePossible(Piece piece) const {
	bool moveFound = false;

	for (int x = 0; !moveFound && x < SIZE; x++) //Hele spelbord aflopen.
		for (int y = 0; !moveFound && y < SIZE; y++)
			moveFound = IsValidMove(piece, x, y);

	return moveFound;
}

/**
 * Kijkt of een bepaalde zet een geldige zet zou zijn.
 *
 * @param piece
 *   De kleur waarvoor gecheckt moet worden.
 * @param x
 *   De x-coördinaat voor de zet.
 * @param y
 *   De y-coördinaat voor de zet.
 * @return
 *   True indien het een geldige zet is, anders false.
 *
 * @author Wim Leers
 */
bool Game::IsValidMove(Piece piece, int x, int y) const {
	return (m_board->IsWithinBounds(x, y) && m_board->GetPiece(x, y) == EMPTY) && (CheckAllDirections(piece, x, y, false));
}

/**
 * Helper functie voor Game::CheckAllDirections().
 *
 * @author Bram Bonn&eacute;
 */
bool Game::CheckRight(Piece piece, int x, int y, bool performMove) const {
	/* Zie commentaar in de functie CheckAllDirections voor een beschrijving
	 * van wat er gebeurt.*/
	bool mogelijkheid = false;
	int xi;

	xi = x+1;
	while (m_board->IsWithinBounds(xi,y) && piece == m_board->GetPiece(xi,y).GetOther())
		 xi++;
	if (xi > x+1 && m_board->IsWithinBounds(xi,y) && m_board->GetPiece(xi,y) == piece) {
		mogelijkheid = true;
		if (performMove)
			FlipPieces(x, y, xi, y, RIGHT);
	}

	return mogelijkheid;
}

/**
 * Helper functie voor Game::CheckAllDirections().
 *
 * @author Bram Bonn&eacute;
 */
bool Game::CheckLeft(Piece piece, int x, int y, bool performMove) const {
	/* Zie commentaar in de functie CheckAllDirections voor een beschrijving
	 * van wat er gebeurt.*/
	bool mogelijkheid = false;
	int xi;

	xi = x-1;
	while (m_board->IsWithinBounds(xi,y) && piece == m_board->GetPiece(xi,y).GetOther())
		 xi--;
	if (xi < x-1 && m_board->IsWithinBounds(xi,y) && m_board->GetPiece(xi,y) == piece) {
		mogelijkheid = true;
		if (performMove)
			FlipPieces(x, y, xi, y, LEFT);
	}

	return mogelijkheid;
}

/**
 * Helper functie voor Game::CheckAllDirections().
 *
 * @author Bram Bonn&eacute;
 */
bool Game::CheckRightUp(Piece piece, int x, int y, bool performMove) const {
	/* Zie commentaar in de functie CheckAllDirections voor een beschrijving
	 * van wat er gebeurt.*/
	bool mogelijkheid = false;
	int xi, yi;

	xi = x+1;
	yi = y+1;
	while (m_board->IsWithinBounds(xi,yi) && piece == m_board->GetPiece(xi,yi).GetOther()) {
		yi++;
		xi++;
	}
	if (xi > x+1 && m_board->IsWithinBounds(xi,yi) && m_board->GetPiece(xi,yi) == piece) {
		mogelijkheid = true;
		if (performMove)
			FlipPieces(x, y, xi, yi, RIGHTUP);
	}

	return mogelijkheid;
}

/**
 * Helper functie voor Game::CheckAllDirections().
 *
 * @author Bram Bonn&eacute;
 */
bool Game::CheckRightDown(Piece piece, int x, int y, bool performMove) const {
	/* Zie commentaar in de functie CheckAllDirections voor een beschrijving
	 * van wat er gebeurt.*/
	bool mogelijkheid = false;
	int xi, yi;

	xi = x+1;
	yi = y-1;
	while (m_board->IsWithinBounds(xi,yi) && piece == m_board->GetPiece(xi,yi).GetOther()) {
		yi--;
		xi++;
	}
	if (xi > x+1 && m_board->IsWithinBounds(xi,yi) && m_board->GetPiece(xi,yi) == piece) {
		mogelijkheid = true;
		if (performMove)
			FlipPieces(x, y, xi, yi, RIGHTDOWN);
	}

	return mogelijkheid;
}

/**
 * Helper functie voor Game::CheckAllDirections().
 *
 * @author Bram Bonn&eacute;
 */
bool Game::CheckLeftUp(Piece piece, int x, int y, bool performMove) const {
	/* Zie commentaar in de functie CheckAllDirections voor een beschrijving
	 * van wat er gebeurt.*/
	bool mogelijkheid = false;
	int xi, yi;

	xi = x-1;
	yi = y+1;
	while (m_board->IsWithinBounds(xi,yi) && piece == m_board->GetPiece(xi,yi).GetOther()) {
		yi++;
		xi--;
	}
	if (xi < x-1 && m_board->IsWithinBounds(xi,yi) && m_board->GetPiece(xi,yi) == piece) {
		mogelijkheid = true;
		if (performMove)
			FlipPieces(x, y, xi, yi, LEFTUP);
	}

	return mogelijkheid;
}

/**
 * Helper functie voor Game::CheckAllDirections().
 *
 * @author Bram Bonn&eacute;
 */
bool Game::CheckLeftDown(Piece piece, int x, int y, bool performMove) const {
	/* Zie commentaar in de functie CheckAllDirections voor een beschrijving
	 * van wat er gebeurt.*/
	bool mogelijkheid = false;
	int xi, yi;

	xi = x-1;
	yi = y-1;
	while (m_board->IsWithinBounds(xi,yi) && piece == m_board->GetPiece(xi,yi).GetOther()) {
		yi--;
		xi--;
	}
	if (xi < x-1 && m_board->IsWithinBounds(xi,yi) && m_board->GetPiece(xi,yi) == piece) {
		mogelijkheid = true;
		if (performMove)
			FlipPieces(x, y, xi, yi, LEFTDOWN);
	}

	return mogelijkheid;
}

/**
 * Helper functie voor Game::CheckAllDirections().
 *
 * @author Bram Bonn&eacute;
 */
bool Game::CheckUp(Piece piece, int x, int y, bool performMove) const {
	/* Zie commentaar in de functie CheckAllDirections voor een beschrijving
	 * van wat er gebeurt.*/
	bool mogelijkheid = false;
	int yi;

	yi = y+1;
	while (m_board->IsWithinBounds(x,yi) && piece == m_board->GetPiece(x,yi).GetOther())
		 yi++;
	if (yi > y+1 && m_board->IsWithinBounds(x,yi) && m_board->GetPiece(x,yi) == piece) {
		mogelijkheid = true;
		if (performMove)
			FlipPieces(x, y, x, yi, UP);
	}

	return mogelijkheid;
}

/**
 * Helper functie voor Game::CheckAllDirections().
 *
 * @author Bram Bonn&eacute;
 */
bool Game::CheckDown(Piece piece, int x, int y, bool performMove) const {
	/* Zie commentaar in de functie CheckAllDirections voor een beschrijving
	 * van wat er gebeurt.*/
	bool mogelijkheid = false;
	int yi;

	yi = y-1;
	while (m_board->IsWithinBounds(x,yi) && piece == m_board->GetPiece(x,yi).GetOther())
		 yi--;
	if (yi < y-1 && m_board->IsWithinBounds(x,yi) && m_board->GetPiece(x,yi) == piece) {
		mogelijkheid = true;
		if (performMove)
			FlipPieces(x, y, x, yi, DOWN);
	}

	return mogelijkheid;
}

/**
 * Kijkt in alle richtingen of er een mogelijke zet is.
 *
 * @param [in] piece
 * 	De kleur waarvoor gecheckt moet worden.
 * @param [in] x
 * 	De x-coördinaat van het element.
 * @param [in] y
 * 	De y-coördinaat van het element.
 * @param [in] performMove
 * 	Zegt of de stukken tussenin daadwerkelijk moeten omgedraaid worden.
 * @return
 *   Of er een mogelijkheid gevonden is.
 *
 * @author Bram Bonn&eacute;
 */
bool Game::CheckAllDirections(Piece piece, int x, int y, bool performMove) const {
	bool mogelijkheid = false;

	/*
	 * Gaat voor alle richtingen aflopen tot het buiten de grenzen komt of er een plaats
	 * met de eigen kleur gevonden wordt. Als dit zo is moet nog gekeken worden of
	 * we binnen de grenzen zitten, of deze plaats niet leeg is, en of er zich elementen tussen bevinden.
	 * Dan zijn we zeker dat
	 * het element de eigen kleur heeft, en dat we alles ertussen kunnen flippen
	 * indien dit gevraagd werd.
	 */

	mogelijkheid = CheckRight(piece, x, y, performMove) || mogelijkheid;
	mogelijkheid = CheckLeft(piece, x, y, performMove) || mogelijkheid;
	mogelijkheid = CheckRightUp(piece, x, y, performMove) || mogelijkheid;
	mogelijkheid = CheckRightDown(piece, x, y, performMove) || mogelijkheid;
	mogelijkheid = CheckLeftUp(piece, x, y, performMove) || mogelijkheid;
	mogelijkheid = CheckLeftDown(piece, x, y, performMove) || mogelijkheid;
	mogelijkheid = CheckUp(piece, x, y, performMove) || mogelijkheid;
	mogelijkheid = CheckDown(piece, x, y, performMove) || mogelijkheid;

	return mogelijkheid;
}

/**
 * Laadt een spel uit het "savegame.rsg" bestand.
 *
 * @return
 *   True indien een saved game werd gevonden en succesvol werd ingeladen.
 *
 * @author Wim Leers
 */
bool Game::Load(void) {
	// Open het bestand en vraag de tabel op.
	m_fileIO.SetFileName(FILENAME);
	m_fileIO.Open();
	m_table = m_fileIO.GetTable();

	// Lees nu iedere rij in, alloceer telkens een Board object, importeer
	// dan telkens de gevens voor dat Board object, en voeg ten slotte dit
	// Board object toe aan de spelgeschiedenis.
	if (m_table.size() == SIZE + 1) {
		FILEIO_TABLE::iterator itTable = m_table.begin();
		FILEIO_ROW::iterator itRow = itTable->begin();
		Board * b = new Board(SIZE);

		// Vul de rij met de inhoud van het bord.
		for (int x = 0; x < SIZE; x++) {
			for (int y = 0; y < SIZE; y++) {
				if (*itRow == "W")
					b->SetPiece(WHITE, x, y);
				else if (*itRow == "B")
					b->SetPiece(BLACK, x, y);
				itRow++;
			}
		}

		// De laatste kolom bevat de zetnummer.
		m_moveNumber = atoi((*itRow).c_str());

		// Voeg het ingelezen spelbord toe aan de geschiedenis.
		m_board = b;

		return true;
	}
	else
		return false;
}

/**
 * Synchroniseert eerst de spelgeschiedenis met de tabelvoorstelling van het
 * bestand en slaat vervolgens het bestand op.
 *
 * @author Wim Leers
 */
void Game::Save(void) {
	// Wis de tabelvoorstelling van het bestand in het geheugen.
	m_table.erase(m_table.begin(), m_table.end());

	// Converteer de stack van spelborden naar een tabelvorm, die opgeslagen
	// kan worden in een bestand.
	FILEIO_ROW row;

	// Zorg dat de rij die we gaan opvullen leeg is.
	row.erase(row.begin(), row.end());

	// Vul de rij met de inhoud van het bord.
	for (int x = 0; x < SIZE; x++) {
		for (int y = 0; y < SIZE; y++) {
			switch (m_board->GetPiece(x, y).Get()) {
				case EMPTY:
					row.push_back("E");
					break;
				case BLACK:
					row.push_back("B");
					break;
				case WHITE:
					row.push_back("W");
					break;
			}
		}
	}

	// Sla in de laatste kolom op aan de hoeveelste zet we zitten.
	char tmp[5];
	sprintf(tmp, "%d", m_moveNumber);
	string moveNumber = tmp;
	row.push_back(moveNumber);

	// Sla de gegeneerde rij op in de tabel en wis dit spelbord.
	m_table.push_back(row);

	// Sla dit bestand op.
	m_fileIO.SetTable(m_table);
	m_fileIO.Save();
}
